//
//  SROtaManager.m
//  sr01sdkProject
//
//  Created by  on 2022/5/16.
//

#import "CAOtaManager.h"
#import "CAParameterStorage.h"

@interface CAOtaManager()
@property(weak, nonatomic)CABLeService *service;

@end

@implementation CAOtaManager
{
    int _step, _nextStep;
    int _expectedValue;
    
    int _chunkSize;
    int _blockStartByte;
    uint16_t _blockSize;
    
//    __weak SRBLeService *_service;
    
    NSMutableData *_fileData;
    NSDate *_uploadStart;
}

- (instancetype)initWithService:(CABLeService *)service
{
    if (self = [super init]) {
        [self registNorify];
        self.service = service;
    }
    
    return self;
}

-(void)setService:(CABLeService *)service
{
    _service = service;
    if (service.mainChipType ==  CA_MAIN_CHIP_14531_00 ||
        service.mainChipType == CA_MAIN_CHIP_14531_01) {
        // sr03 sr09
        [[CAParameterStorage getInstance] setSr09Param];
        return;
    }
    
    if (service.mainChipType ==  CA_MAIN_CHIP_14592) {
        
        [[CAParameterStorage getInstance] setSr23Param];
        return;

    }
    DebugNSLog(@"OTA 芯片型号没对应");
    
}


-(void)setOtaImgFileUrl:(NSURL *)otaImgFileUrl
{
    _otaImgFileUrl = otaImgFileUrl;
}


-(void)startOta:(NSURL *)fileUtl
{
    [self setOtaImgFileUrl:fileUtl];
    
    // 开始
    _step = 1;
    [self doStep];
}



-(void)registNorify {
    // Enable notifications for BLE events
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(didDisconnectFromDevice:)
                                                 name:CABluetoothManagerDisconnectedFromDevice
                                               object:nil];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(didUpdateValueForCharacteristic:)
                                                 name:CAGenericServiceManagerDidReceiveValue
                                               object:nil];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(didSendValueForCharacteristic:)
                                                 name:CAGenericServiceManagerDidSendValue
                                               object:nil];

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(onBleOperationError:)
                                                 name:CAGenericServiceManagerWriteError
                                               object:nil];
}

- (void) didDisconnectFromDevice: (NSNotification*)notification {
    if (_step != 8) {
//        [self showAlertWarningWithTitle:_step != 7 ? @"Upload Failed" : @"Device Disconnected" Msg:@"The connection to the remote device was lost." btnOk:nil OkBLk:nil CancelBtn:@"OK" CancelBlk:nil Compelete:nil];
//
        if ([self.bleOtaDelegate respondsToSelector:@selector(caOtaFinish:)]) {
            [self.bleOtaDelegate caOtaFinish:NO]; // 回调失败
        }
    }
}

- (void) didUpdateValueForCharacteristic: (NSNotification*)notification {
    CBCharacteristic *characteristic = (CBCharacteristic*) notification.object;
    if ([characteristic.UUID isEqual:CABLeService.SPOTA_SERV_STATUS_CBUUID]) {
        char value;
        [characteristic.value getBytes:&value length:sizeof(char)];
        
     
        
        if (_expectedValue != 0) {
            // Check if value equals the expected value
            if (value == _expectedValue) {
                // If so, continue with the next step
                _step = _nextStep;
                
                _expectedValue = 0; // Reset
                
                [self doStep];
            } else {
                // Else display an error message
               
                
//                [self showAlertWarningWithTitle:@"Error" Msg:message btnCancel:@"OK" Compelete:nil];
                if ([self.bleOtaDelegate respondsToSelector:@selector(caOtaError:)]) {
                    [self.bleOtaDelegate caOtaError:value]; //回调错误
                }
                
                if ([self.bleOtaDelegate respondsToSelector:@selector(caOtaFinish:)]) {
                    [self.bleOtaDelegate caOtaFinish:NO];
                }
                
                _expectedValue = 0; // Reset
            }
        }
    }
}

- (void) didSendValueForCharacteristic: (NSNotification*)notification {
    if (_step && _step != 7) {
        [self doStep];
    }
}

- (void) onBleOperationError: (NSNotification*)notification {
    [self debug:[NSString stringWithFormat:@"Error in BLE operation on characteristic %@", ((CBCharacteristic*)notification.object).UUID] UILog:YES];
    if (_step != 8) {

        if ([self.bleOtaDelegate respondsToSelector:@selector(caOtaFinish:)]) {
            [self.bleOtaDelegate caOtaFinish:NO]; // 回调完成
        }
//        [self showAlertWarningWithTitle:@"Upload Failed" Msg:@"The firmware upload procedure encountered an error." btnCancel:@"OK" Compelete:nil];
    }
}

- (void) doStep {
    [self debug:[NSString stringWithFormat:@"*** Next step: %d", _step] UILog:NO];
    
    switch (_step) {
        case 1: {
            // Step 1: Set memory type
            _step = 0;
            _expectedValue = 0x10;
            _nextStep = 2;
            _uploadStart = [NSDate date];

            uint32_t _memDevData = ([CAParameterStorage getInstance].memoryType << 24) | [CAParameterStorage getInstance].memoryBank;
            [self debug:[NSString stringWithFormat:@"Set SPOTA_MEM_DEV: %#010x", _memDevData] UILog:YES];
            NSData *memDevData = [NSData dataWithBytes:&_memDevData length:sizeof(uint32_t)];
            [self.service otaWriteValue:CABLeService.SPOTA_SERVICE_CBUUID characteristicUUID:CABLeService.SPOTA_MEM_DEV_CBUUID data:memDevData];
            break;
        }
            
        case 2: {
            // Step 2: Set memory params
            uint32_t _memInfoData = 0;
            if ([CAParameterStorage getInstance].memoryType == MEM_TYPE_SUOTA_SPI) {
                _memInfoData = ([CAParameterStorage getInstance].spiMISOGPIO << 24) | ([CAParameterStorage getInstance].spiMOSIGPIO << 16) | ([CAParameterStorage getInstance].spiCSGPIO << 8) | [CAParameterStorage getInstance].spiSCKGPIO;
            }
//            else if ([ParameterStorage getInstance].memoryType == MEM_TYPE_SUOTA_I2C) {
//                _memInfoData = ([ParameterStorage getInstance].i2cAddress << 16) | ([ParameterStorage getInstance].i2cSCLGPIO << 8) | self.i2cSDAGPIO;
//            }
            [self debug:[NSString stringWithFormat:@"Set SPOTA_GPIO_MAP: %#010x", _memInfoData] UILog:YES];
            NSData *memInfoData = [NSData dataWithBytes:&_memInfoData length:sizeof(uint32_t)];
            
            _step = 3;
            [self.service otaWriteValue:CABLeService.SPOTA_SERVICE_CBUUID characteristicUUID:CABLeService.SPOTA_GPIO_MAP_CBUUID data:memInfoData];
            break;
        }
            
        case 3: {
            // Load patch data
            [self debug:[NSString stringWithFormat:@"Loading data from %@", _otaImgFileUrl.absoluteString.stringByRemovingPercentEncoding] UILog:YES];
            _fileData = [[NSData dataWithContentsOfURL:_otaImgFileUrl] mutableCopy];
            [self appendChecksum];
            [self debug:[NSString stringWithFormat:@"Upload size: %d bytes", (int) [_fileData length]] UILog:YES];
            
            // Step 3: Set patch length
            _chunkSize = MIN(self.service.suotaPatchDataSize, self.service.suotaMtu - 3);
            _blockSize = MAX(_blockSize, _chunkSize);
            if (_blockSize > _fileData.length) {
                _blockSize = _fileData.length;
                if (_chunkSize > _blockSize)
                    _chunkSize = _blockSize;
            }
            _blockStartByte = 0;
            [self debug:[NSString stringWithFormat:@"Chunk size: %d bytes", _chunkSize] UILog:YES];

            _step = 4;
            [self doStep];
            break;
        }
            
        case 4: {
            // Set patch length
            [self debug:[NSString stringWithFormat:@"Set SPOTA_PATCH_LEN: %d", _blockSize] UILog:YES];
            NSData *patchLengthData = [NSData dataWithBytes:&_blockSize length:sizeof(uint16_t)];
            
            _step = 5;
            
            [self.service otaWriteValue:CABLeService.SPOTA_SERVICE_CBUUID characteristicUUID:CABLeService.SPOTA_PATCH_LEN_CBUUID data:patchLengthData];
            break;
        }
            
        case 5: {
            // Send current block in chunks of 20 bytes
            if (_blockStartByte == 0)
                [self debug:@"Upload procedure started" UILog:YES];

            _step = 0;
            _expectedValue = 0x02;
            _nextStep = 5;
            
            int dataLength = (int) [_fileData length];
            int chunkStartByte = 0;
            
            while (chunkStartByte < _blockSize) {
                
                // Check if we have less than current block-size bytes remaining
                int bytesRemaining = _blockSize - chunkStartByte;
                int currChunkSize = bytesRemaining >= _chunkSize ? _chunkSize : bytesRemaining;

                [self debug:[NSString stringWithFormat:@"Sending bytes %d to %d (%d/%d) of %d", _blockStartByte + chunkStartByte + 1, _blockStartByte + chunkStartByte + currChunkSize, chunkStartByte + currChunkSize, _blockSize, dataLength] UILog:NO];
                
                double progress = (double)(_blockStartByte + chunkStartByte + currChunkSize) / (double)dataLength;
                
                if ([self.bleOtaDelegate respondsToSelector:@selector(caOtaUpdateProgress:)]) {
                    [self.bleOtaDelegate caOtaUpdateProgress:progress];
                }
                

//
                // Step 4: Send next n bytes of the patch
                char bytes[currChunkSize];
                [_fileData getBytes:bytes range:NSMakeRange(_blockStartByte + chunkStartByte, currChunkSize)];
                NSData *byteData = [NSData dataWithBytes:bytes length:currChunkSize];
                
                // On to the chunk
                chunkStartByte += currChunkSize;
                
                // Check if we are passing the current block
                if (chunkStartByte >= _blockSize) {
                    // Prepare for next block
                    _blockStartByte += _blockSize;
                    
                    int bytesRemaining = dataLength - _blockStartByte;
                    if (bytesRemaining == 0) {
                        _nextStep = 6;
                        
                    } else if (bytesRemaining < _blockSize) {
                        _blockSize = bytesRemaining;
                        _nextStep = 4; // Back to step 4, setting the patch length
                    }
                }
            //  写入升级包数据 不回包写入
                [self.service otaWriteValueWithoutResponse:CABLeService.SPOTA_SERVICE_CBUUID characteristicUUID:CABLeService.SPOTA_PATCH_DATA_CBUUID data:byteData];
            }
            
            break;
        }
            
        case 6: {
            // Send SUOTA END command
            _step = 0;
            _expectedValue = 0x02;
            _nextStep = 7;

            uint32_t suotaEnd = 0xFE000000;
            [self debug:[NSString stringWithFormat:@"Send SUOTA END command: %#010x", suotaEnd] UILog:YES];
            NSData *suotaEndData = [NSData dataWithBytes:&suotaEnd length:sizeof(uint32_t)];
            [self.service otaWriteValue:CABLeService.SPOTA_SERVICE_CBUUID characteristicUUID:CABLeService.SPOTA_MEM_DEV_CBUUID data:suotaEndData];
            break;
        }
            
        case 7: {
            [self debug:@"Upload completed" UILog:YES];
            NSTimeInterval elapsed = [[NSDate date] timeIntervalSinceDate:_uploadStart];
            [self debug:[NSString stringWithFormat:@"Elapsed time: %.3f", elapsed] UILog:YES];
   
            // 发送重启命令
            // Send reboot signal to device
            self->_step = 8;
            uint32_t suotaReboot = 0xFD000000;
            [self debug:[NSString stringWithFormat:@"Send SUOTA REBOOT command: %#010x", suotaReboot] UILog:YES];
            NSData *suotaRebootData = [NSData dataWithBytes:&suotaReboot length:sizeof(uint32_t)];
            [self.service otaWriteValue:CABLeService.SPOTA_SERVICE_CBUUID characteristicUUID:CABLeService.SPOTA_MEM_DEV_CBUUID data:suotaRebootData];
            
//            WEAKSELF
//            [self showAlertWarningWithTitle:@"Device has been updated" Msg:@"Do you wish to reboot the device?" btnOk:@"Yes, reboot" OkBLk:^{
//                STRONGSELF
//                // Send reboot signal to device
//                strongSelf->_step = 8;
//                uint32_t suotaReboot = 0xFD000000;
//                [strongSelf debug:[NSString stringWithFormat:@"Send SUOTA REBOOT command: %#010x", suotaReboot] UILog:YES];
//                NSData *suotaRebootData = [NSData dataWithBytes:&suotaReboot length:sizeof(uint32_t)];
//                [strongSelf.service otaWriteValue:SRBLeService.SPOTA_SERVICE_CBUUID characteristicUUID:SRBLeService.SPOTA_MEM_DEV_CBUUID data:suotaRebootData];
//            } CancelBtn:@"No" CancelBlk:nil Compelete:nil];
            
            if ([self.bleOtaDelegate respondsToSelector:@selector(caOtaFinish:)]) {
                [self.bleOtaDelegate caOtaFinish:YES]; // 回调完成
            }
            
            break;
        }
            
        case 8: {
            // Go back to overview of devices
//            [self dismissViewControllerAnimated:YES completion:nil];
//            [self.navigationController popToRootViewControllerAnimated:YES];
            break;
        }
    }
}

- (void) appendChecksum {
    uint8_t crc_code = 0;
    
    const char *bytes = [_fileData bytes];
    for (int i = 0; i < [_fileData length]; i++) {
        crc_code ^= bytes[i];
    }
    
    [self debug:[NSString stringWithFormat:@"Checksum for file: %#4x", crc_code] UILog:YES];
    
    [_fileData appendBytes:&crc_code length:sizeof(uint8_t)];
}

- (void) debug:(NSString*)message UILog:(BOOL)uiLog {


    DebugNSLog(@"%@", message);

    
}

-(void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}



@end
